package main

import (
    "gitee.com/mayiweb/gohs"
)

// 获得交易请求编号
func GetTradeRequestId() int {
    Ctp.TradeRequestId += 1
    return Ctp.TradeRequestId
}

// 获得 API 错误信息
func (p *HsTradeSpi) GetApiErrorMsg(iResult int) string {
    return GbkToUtf8(Ctp.TradeApi.GetApiErrorMsg(iResult))
}

// 交易系统错误通知
func (p *HsTradeSpi) IsErrorRspInfo(pRspInfo gohs.CHSRspInfoField) bool {

    // 容错处理 pRspInfo ，部分响应函数中，pRspInfo 为空
    if IsNullPointer(pRspInfo) {

        return false

    } else {

        ErrorID := GetSwigInt32(pRspInfo.GetErrorID())

        // 如果ErrorID != 0, 说明收到了错误的响应
        bResult := (ErrorID != 0)
        if bResult {
            LogPrintf("ErrorID=%v ErrorMsg=%v\n", ErrorID, GbkToUtf8(pRspInfo.GetErrorMsg()))

            // 如果初始化未完成则退出程序，防止卡在某个流程
            if !Ctp.IsTradeInitFinish {
                Exit()
            }
        }

        return bResult
    }
}

// 发送请求日志（仅查询类的函数需要调用）
func (p *HsTradeSpi) ReqMsg(Msg string) {

    // 交易程序未初始化完成时，执行查询类的函数需要有1.5秒间隔
    if !Ctp.IsTradeInitFinish {
        // Sleep(1500)
    }

    Println("")
    LogPrintln(Msg)
}

// 发送请求失败日志
func (p *HsTradeSpi) ReqFailMsg(Msg string, iResult int) {
    Printf("%v [%d: %s]\n", Msg, iResult, p.GetApiErrorMsg(iResult))
}

// 当客户端与交易后台通信连接断开时，该方法被调用。当发生这个情况后，API会自动重新连接，客户端可不做处理。
// 服务器已断线，该函数也会被调用。【api 会自动初始化程序，并重新登陆】
func (p *HsTradeSpi) OnFrontDisconnected(nReason int) {

    if Ctp.IsTradeInit {

        Ctp.IsTradeInit       = false
        Ctp.IsTradeLogin      = false
        Ctp.IsTradeInitFinish = false

        LogPrintln("交易服务器已断线，尝试重新连接中...")
    }
}

// 当客户端与交易后台建立起通信连接时（还未登录前），该方法被调用。
func (p *HsTradeSpi) OnFrontConnected() {

    InitStr := "-------------------------------------------------------------------------------------------------\n" +
                 "- 交易系统初始化成功，API 版本：" + gohs.GetTradeApiVersion() + "\n" +
                 "-------------------------------------------------------------------------------------------------"
    Println(InitStr)

    Ctp.IsTradeInit = true

    // 填写了 AppID 与 AuthCode 则进行客户端认证
    if Ctp.AppID != "" && Ctp.AuthCode != "" {
        p.ReqAuthenticate()
    } else {
        p.ReqUserLogin()
    }
}

// 客户端认证
func (p *HsTradeSpi) ReqAuthenticate() {

    p.ReqMsg("客户端认证中...")

    req := gohs.NewCHSReqAuthenticateField()
    req.SetAccountID(Ctp.AccountID)
    req.SetPassword(Ctp.Password)
    req.SetAppID(Ctp.AppID)
    req.SetAuthCode(Ctp.AuthCode)

    iResult := Ctp.TradeApi.ReqAuthenticate(req, GetTradeRequestId())
    if iResult != SDK_OK {
        p.ReqFailMsg("发送客户端认证请求失败！", iResult)
    }
}

// 客户端认证响应
func (p *HsTradeSpi) OnRspAuthenticate(pRspAuthenticate gohs.CHSRspAuthenticateField, pRspInfo gohs.CHSRspInfoField, nRequestID int, bIsLast bool) {

    if bIsLast && !p.IsErrorRspInfo(pRspInfo) {

        LogPrintln("客户端认证成功！")

        p.ReqUserLogin()
    }
}

// 客户登录
func (p *HsTradeSpi) ReqUserLogin() {

    p.ReqMsg("用户登录中...")

    req := gohs.NewCHSReqUserLoginField()
    req.SetAccountID(Ctp.AccountID)
    req.SetPassword(Ctp.Password)
    req.SetUserApplicationType('7')

    iResult := Ctp.TradeApi.ReqUserLogin(req, GetTradeRequestId())
    if iResult != SDK_OK {
        p.ReqFailMsg("发送用户登录请求失败！", iResult)
    }
}

// 客户登录响应
func (p *HsTradeSpi) OnRspUserLogin(pRspUserLogin gohs.CHSRspUserLoginField, pRspInfo gohs.CHSRspInfoField, nRequestID int, bIsLast bool) {

    if bIsLast && !p.IsErrorRspInfo(pRspInfo) {

        // 获得登录成功结构体数据
        sLogin := GetLoginStruct(pRspUserLogin)

        LogPrintf("登录成功，当前交易日：%v\n", sLogin.TradingDay)

        Ctp.IsTradeLogin = true

        p.ReqBillConfirm()
    }
}

// 客户账单确认
func (p *HsTradeSpi) ReqBillConfirm() int {

    p.ReqMsg("客户账单确认中...")

    req := gohs.NewCHSReqBillConfirmField()

    iResult := Ctp.TradeApi.ReqBillConfirm(req, GetTradeRequestId())
    if iResult != SDK_OK {
        p.ReqFailMsg("客户账单确认失败！", iResult)
    }

    return iResult
}

// 客户账单确认响应
func (p *HsTradeSpi) OnRspBillConfirm(pRspBillConfirm gohs.CHSRspBillConfirmField, pRspInfo gohs.CHSRspInfoField, nRequestID int, bIsLast bool) {

    if bIsLast && !p.IsErrorRspInfo(pRspInfo) {
        LogPrintln("客户账单确认成功")

        p.ReqQryInstrument()
    }
}

// 查询合约
func (p *HsTradeSpi) ReqQryInstrument() int {

    p.ReqMsg("查询合约中...")

    req := gohs.NewCHSReqQryInstrumentField()
    req.SetExchangeID("")
    req.SetInstrumentID("")

    iResult := Ctp.TradeApi.ReqQryInstrument(req, GetTradeRequestId())
    if iResult != SDK_OK {
        p.ReqFailMsg("查询合约失败！", iResult)
    }

    return iResult
}

// 查询合约响应
func (p *HsTradeSpi) OnRspQryInstrument(pInstrument gohs.CHSRspQryInstrumentField, pRspInfo gohs.CHSRspInfoField, nRequestID int, bIsLast bool) {

    if !p.IsErrorRspInfo(pRspInfo) && !IsNullPointer(pInstrument) {

        // 获得合约结构体数据
        GetInstrumentStruct(pInstrument)
    }

    if bIsLast {

        LogPrintf("获得合约记录 %v 条\n", MapInstruments.Size())

        if !Ctp.IsTradeInitFinish {
            // 资金账户查询
            p.ReqQryTradingAccount()
        }
    }
}

// 查询资金账户
func (p *HsTradeSpi) ReqQryTradingAccount() int {

    p.ReqMsg("查询资金账户中...")

    req := gohs.NewCHSReqQryTradingAccountField()

    iResult := Ctp.TradeApi.ReqQryTradingAccount(req, GetTradeRequestId())
    if iResult != SDK_OK {
        p.ReqFailMsg("查询资金账户失败！", iResult)
    }

    return iResult
}

// 查询资金账户响应
func (p *HsTradeSpi) OnRspQryTradingAccount(pTradingAccount gohs.CHSRspQryTradingAccountField, pRspInfo gohs.CHSRspInfoField, nRequestID int, bIsLast bool) {

    if !p.IsErrorRspInfo(pRspInfo) && !IsNullPointer(pTradingAccount) {

        // 获得资金账户结构体数据
        GetAccountStruct(pTradingAccount)
    }

    if (bIsLast) {

        // 获得资金账户信息
        mAccount := GetAccount()

        formatStr := "-------------------------------------------------------------------------------------------------\n" +
                "- 资金账号：%v\n" +
                "- 期初资金：%.2f \n" +
                "- 动态权益：%.2f \n" +
                "- 可用资金：%.2f \n" +
                "- 持仓盈亏：%.2f \n" +
                "- 平仓盈亏：%.2f \n" +
                "- 手续费  ：%.2f \n" +
                "-------------------------------------------------------------------------------------------------"

        Println(Sprintf(formatStr, mAccount.AccountID, mAccount.YdFundEquity, mAccount.Equity, mAccount.AvailableBalance, mAccount.PositionProfit, mAccount.CloseProfit, mAccount.Commission))

        if !Ctp.IsTradeInitFinish {
            // 请求查询报单
            p.ReqQryOrder()
        }
    }
}

// 查询报单
func (p *HsTradeSpi) ReqQryOrder() int {

    p.ReqMsg("查询报单中...")

    req := gohs.NewCHSReqQryOrderField()

    iResult := Ctp.TradeApi.ReqQryOrder(req, GetTradeRequestId())
    if iResult != SDK_OK {
        p.ReqFailMsg("查询报单失败！", iResult)
    }

    return iResult
}

// 查询报单响应
func (p *HsTradeSpi) OnRspQryOrder(pOrder gohs.CHSOrderField, pRspInfo gohs.CHSRspInfoField, nRequestID int, bIsLast bool) {

    if !p.IsErrorRspInfo(pRspInfo) && !IsNullPointer(pOrder) {

        // 获得报单结构体数据
        GetOrderStruct(pOrder)
    }

    if (bIsLast) {

        str := "-------------------------------------------------------------------------------------------------\n"

        OrderNoTradeSize := 0

        mOrders := MapOrders.GetAll()
        for _, v := range mOrders {
            val := v.(OrderStruct)

            // 输出 未成交、部分成交 的报单
            if val.OrderStatus == string(gohs.HS_OS_Reported) || val.OrderStatus == string(gohs.HS_OS_PartsTraded) {
                OrderNoTradeSize += 1
                str += Sprintf("- 合约：%v %v:%v 数量：%v 价格：%v 报单编号：%v (%v)\n", StrAfterSpace(val.InstrumentID, 16), val.DirectionTitle, StrAfterSpace(val.OffsetFlagTitle, 11), StrAfterSpace(IntToString(val.OrderVolume), 6), StrAfterSpace(Sprintf("%.4f", val.OrderPrice), 12), TrimSpace(val.OrderSysID), val.OrderStatusTitle)
            }
        }

        str += Sprintf("- 共有报单记录 %v 条，未成交 %v 条\n", MapOrders.Size(), OrderNoTradeSize)
        str += "-------------------------------------------------------------------------------------------------"

        Println(str)

        if !Ctp.IsTradeInitFinish {
            p.ReqQryPosition()
        }
    }
}

// 查询持仓汇总
func (p *HsTradeSpi) ReqQryPosition() int {

    p.ReqMsg("查询持仓汇总中...")

    req := gohs.NewCHSReqQryPositionField()

    iResult := Ctp.TradeApi.ReqQryPosition(req, GetTradeRequestId())
    if iResult != SDK_OK {
        p.ReqFailMsg("查询持仓汇总失败！", iResult)
    }

    return iResult
}

// 查询持仓汇总响应
func (p *HsTradeSpi) OnRspQryPosition(pPosition gohs.CHSRspQryPositionField, pRspInfo gohs.CHSRspInfoField, nRequestID int, bIsLast bool) {

    if !p.IsErrorRspInfo(pRspInfo) && !IsNullPointer(pPosition) {

        // 获得持仓结构体数据
        GetPositionStruct(pPosition)
    }

    if bIsLast {

        PositionStr := "-------------------------------------------------------------------------------------------------\n"

        // 输出当前持仓
        mPositions := MapPositions.GetAll()
        for _, v := range mPositions {

            val := v.(PositionStruct)

            if val.PositionVolume != 0 {

                PositionStr += Sprintf("- 合约：%v  \t%v  \t持仓：%v  \t均价：%v  \t盈亏：%v\n", val.InstrumentName, val.DirectionTitle, val.PositionVolume, Sprintf("%.4f", val.PositionPrice), Sprintf("%.2f", val.PositionProfit))
            }
        }

        PositionStr += "-------------------------------------------------------------------------------------------------\n"

        Println(PositionStr)


        if !Ctp.IsTradeInitFinish {

            // 交易程序初始化流程走完了
            Ctp.IsTradeInitFinish = true

            // 订阅行情
            Subscribe := []string{"rb2305"}
            MdSpi.SubscribeMarketData(Subscribe)
        }
    }
}

// 报单录入-开仓
// string  @InstrumentID  合约代码
// float64 @Price         价格
// int     @Volume        数量
// byte    @Direction     买卖方向(买:OrderBuy、卖：OrderSell)
func (p *HsTradeSpi) OrderOpen(Input InputOrderStruct) int {

    // 开仓标识
    Input.OffsetFlag = gohs.HS_OF_Open

    // 限价单最大报单量
    MaxLimitOrderVolume := 50

    OrderRef := 0

    Instrument, InstrumentOk := GetInstrumentInfo(Input.InstrumentID)
    if InstrumentOk {
        MaxLimitOrderVolume = Instrument.MaxLimitOrderVolume
    }

    // 自动拆单（每次下单数不能超过数量限额）
    for volume := Input.Volume; volume > 0; volume -= MaxLimitOrderVolume {

        // 剩余数量大于 最大报单量，则使用最大报单量
        if volume > MaxLimitOrderVolume {
            Input.Volume = MaxLimitOrderVolume
        } else {
            Input.Volume = volume
        }

        OrderRef = p.ReqOrderInsert(Input)

        // 间隔0.25秒申报第二单
        Sleep(250)
    }

    return OrderRef
}

// 报单录入-平仓
// string  @InstrumentID  合约代码
// float64 @Price         价格
// int     @Volume        数量
// byte    @Direction     买卖方向(买:OrderBuy、卖：OrderSell)
func (p *HsTradeSpi) OrderClose(Input InputOrderStruct) int {

    // 平仓标识
    Input.OffsetFlag = gohs.HS_OF_Close

    // 限价单最大报单量
    MaxLimitOrderVolume := 50

    OrderRef := 0

    Instrument, InstrumentOk := GetInstrumentInfo(Input.InstrumentID)
    if InstrumentOk {
        MaxLimitOrderVolume = Instrument.MaxLimitOrderVolume

        if Instrument.ExchangeID == "F3" {
            // 上期所（默认平今仓）
            Input.OffsetFlag = gohs.HS_OF_CloseToday
        } else {
            // 非上期所，不用区分今昨仓，直接使用平仓即可
            Input.OffsetFlag = gohs.HS_OF_Close
        }
    }

    // 自动拆单（每次下单数不能超过数量限额）
    for volume := Input.Volume; volume > 0; volume -= MaxLimitOrderVolume {

        // 剩余数量大于 最大报单量，则使用最大报单量
        if volume > MaxLimitOrderVolume {
            Input.Volume = MaxLimitOrderVolume
        } else {
            Input.Volume = volume
        }

        OrderRef = p.ReqOrderInsert(Input)

        // 间隔0.25秒申报第二单
        Sleep(250)
    }

    return OrderRef
}

// 报单录入
func (p *HsTradeSpi) ReqOrderInsert(Input InputOrderStruct) int {

    // 检查合约信息是否存在
    Instrument, InstrumentOk := GetInstrumentInfo(Input.InstrumentID)
    if !InstrumentOk {
        LogPrintln("合约信息", Input.InstrumentID, "不存在，禁止报单录入！")
        return -1
    }

    // 检查订单申报（限制：每秒不能超过5笔）
    CheckOrderDeclare()

    RequestId := GetTradeRequestId()

    req := gohs.NewCHSReqOrderInsertField()

    req.SetOrderRef(IntToString(RequestId))
    req.SetExchangeID(Instrument.ExchangeID)
    req.SetInstrumentID(Input.InstrumentID)
    req.SetHedgeType(gohs.HS_HT_Speculation)
    req.SetOrderCommand(SetSwigInt32(int32(gohs.HS_CT_Limit)))
    req.SetDirection(Input.Direction)
    req.SetOffsetFlag(Input.OffsetFlag)
    req.SetOrderPrice(Input.Price)
    req.SetOrderVolume(float64(Input.Volume))

    // 报单有效期
    if Input.Second != 0 {
        MapOrderRefAction.Set(IntToString(RequestId), Input)
    }

    iResult := Ctp.TradeApi.ReqOrderInsert(req, RequestId)
    if iResult != SDK_OK {
        p.ReqFailMsg("报单录入失败！", iResult)
    }

    return RequestId
}

// 撤单-快捷接口
func (p *HsTradeSpi) OrderCancel(OrderSysID string) int {

    // 检查报单记录是否存在
    mOrderVal, mOrderOk := MapOrders.Get(OrderSysID)
    if !mOrderOk {
        LogPrintln("报单编号", OrderSysID, "不存在，撤单失败！")
        return -1
    }

    mOrder := mOrderVal.(OrderStruct)

    var Input OrderStruct

    Input.OrderSysID = mOrder.OrderSysID
    Input.ExchangeID = mOrder.ExchangeID
    Input.SessionID  = mOrder.SessionID
    Input.OrderRef   = mOrder.OrderRef

    return p.ReqOrderAction(Input)
}

// 报单撤单
func (p *HsTradeSpi) ReqOrderAction(Input OrderStruct) int {

    // 检查订单申报
    CheckOrderDeclare()

    req := gohs.NewCHSReqOrderActionField()

    req.SetOrderSysID(Input.OrderSysID)
    req.SetExchangeID(Input.ExchangeID)
    req.SetSessionID(SetSwigInt32(Input.SessionID))
    req.SetOrderRef(Input.OrderRef)

    iResult := Ctp.TradeApi.ReqOrderAction(req, GetTradeRequestId())
    if iResult != SDK_OK {
        p.ReqFailMsg("报单撤单失败！", iResult)
    }

    return iResult
}

// 报单错误
func (p *HsTradeSpi) OnRspErrorOrderInsert(pRspOrderInsert gohs.CHSRspOrderInsertField, pRspInfo gohs.CHSRspInfoField, nRequestID int, bIsLast bool) {

    if !p.IsErrorRspInfo(pRspInfo) && !IsNullPointer(pRspOrderInsert) {

        str := "----------------------------------------------------------------------\n" +
            "- 报单错误  " + GetInstrumentName(pRspOrderInsert.GetInstrumentID()) + "（" + pRspOrderInsert.GetInstrumentID() + "）\n" +
            "- 报单时间：" + HsTime(Int32ToString(GetSwigInt32(pRspOrderInsert.GetInsertTime()))) + "      报单引用：" + pRspOrderInsert.GetOrderRef() + "\n" +
            "- 报单方向：" + GetDirectionTitle(string(pRspOrderInsert.GetDirection())) + "            报单价格：" + Sprintf("%.4f", pRspOrderInsert.GetOrderPrice()) + "\n" +
            "- 报单开平：" + GetOffsetFlagTitle(string(pRspOrderInsert.GetOffsetFlag())) + "          报单数量：" + Float64ToString(pRspOrderInsert.GetOrderVolume()) + "\n" +
            "- 报单状态：" + GetOrderStatusTitle(string(pRspOrderInsert.GetOrderStatus())) + "\n" +
            "----------------------------------------------------------------------"
        Println(str)

        // 更新持仓数据及资金账户
        AddQueryTask("ReqQryPosition")
        AddQueryTask("ReqQryTradingAccount")
    }
}

// 报单回报
func (p *HsTradeSpi) OnRtnOrder(pRtnOrder gohs.CHSOrderField) {

    if !IsNullPointer(pRtnOrder) {

        // 获得报单结构体数据
        sOrder := GetOrderStruct(pRtnOrder)

        // 自动操作日志
        AutoTradeLog := ""

        // 检查是否存在 报单 OrderRef 事件【如果拿不到 OrderRef，则功能会失效】
        mOrderRefActionVal, mOrderRefActionOk := MapOrderRefAction.Get(sOrder.OrderRef)
        if mOrderRefActionOk {

            mOrderRefAction := mOrderRefActionVal.(InputOrderStruct)

            // 只有已报状态才处理倒计时
            if sOrder.OrderStatus == "2" {

                sOrder.Second = mOrderRefAction.Second

                MapOrderTask.Set(sOrder.OrderRef, sOrder)
            }

            // 自动撤单操作
            if sOrder.OrderStatus == "6" {
                AutoTradeLog = "- 操作日志：报单到达有效期 " + IntToString(mOrderRefAction.Second) + " 秒未成交，自动撤单\n"
            }
        }

        // 只输出 未成交和撤单的报单
        if StrInArray(sOrder.OrderStatus, []string{"2", "5", "6"}) {

            str := "-------------------------------------------------------------------------------------------------\n" +
                "- 报单通知  " + sOrder.InstrumentName + "（" + sOrder.InstrumentID + "）\n" +
                "- 报单时间：" + sOrder.InsertTime + "      报单编号：" + StrAfterSpace(TrimSpace(sOrder.OrderSysID), 20) + "报单引用：" + TrimSpace(sOrder.OrderRef) + "\n" +
                "- 报单方向：" + sOrder.DirectionTitle + "            报单价格：" + Sprintf("%.4f", sOrder.OrderPrice) + "\n" +
                "- 报单开平：" + sOrder.OffsetFlagTitle + "          报单数量：" + IntToString(sOrder.OrderVolume) + "\n" +
                "- 报单状态：" + sOrder.OrderStatusTitle + "\n" +
                AutoTradeLog +
                "-------------------------------------------------------------------------------------------------"
            Println(str)
        }

        // 更新资金账户
        AddQueryTask("ReqQryTradingAccount")
    }
}

// 成交回报
func (p *HsTradeSpi) OnRtnTrade(pTrade gohs.CHSTradeField) {

    if !IsNullPointer(pTrade) {

        // 获得报单成交结构体数据
        sTrade := GetTradeStruct(pTrade)

        str := "-------------------------------------------------------------------------------------------------\n" +
            "- 成交通知  " + sTrade.InstrumentName + "（" + sTrade.InstrumentID + "）\n" +
            "- 成交时间：" + Sprintf("%v", sTrade.TradeTime) + "      报单编号：" + StrAfterSpace(sTrade.OrderSysID, 20) + "成交编号：" + sTrade.TradeID + "\n" +
            "- 成交方向：" + sTrade.DirectionTitle + "            成交价格：" + Sprintf("%.4f", sTrade.TradePrice) + "\n" +
            "- 成交开平：" + sTrade.OffsetFlagTitle + "          成交数量：" + Sprintf("%v", sTrade.TradeVolume) + "\n" +
            "-------------------------------------------------------------------------------------------------"
        Println(str)

        // 更新持仓数据及资金账户
        AddQueryTask("ReqQryPosition")
        AddQueryTask("ReqQryTradingAccount")
    }
}


// 获得合约详情信息
func GetInstrumentInfo(InstrumentID string) (InstrumentStruct, bool) {
    if v, ok := MapInstruments.Get(InstrumentID); ok {
        val := v.(InstrumentStruct)

        if val.UnderlyingInstrID == "" {
            val.UnderlyingInstrID = val.InstrumentID
        }

        return val, true
    } else {
        var mInstrumentInfo InstrumentStruct
        return mInstrumentInfo, false
    }
}

/**
 * 计算盈亏
 *
 * @param   InstrumentID  string  合约
 * @param   OpenPrice     float64 开仓价格
 * @param   LastPrice     float64 最新价|平仓价格
 * @param   Number        int     数量
 * @param   Direction     string  持仓方向[1：买，2：卖]
 */
func GetPositionProfit(InstrumentID string, OpenPrice float64, LastPrice float64, Number int, Direction string) float64 {

    InstrumentInfo, _ := GetInstrumentInfo(InstrumentID)

    if Direction == "1" {
        return ((LastPrice - OpenPrice) * float64(InstrumentInfo.VolumeMultiple)) * float64(Number)
    } else {
        return ((OpenPrice - LastPrice) * float64(InstrumentInfo.VolumeMultiple)) * float64(Number)
    }
}

// 获得资金账户信息
func GetAccount() AccountStruct {

    var sAccount AccountStruct

    mAccountVal, mAccountOk := MapAccount.Get(Ctp.AccountID)
    if !mAccountOk {
        return sAccount
    }

    return mAccountVal.(AccountStruct)
}